#include "CurlClient.h"
#include <stdlib.h>
#include <memory>
#include <string.h>


#define  CURL_TIMEOUT_ID   (201)
#define  CURL_TIMEOUT_MS   5000

#define CURL_MAX_REQUEST_PERFORM 1024
#define CURL_MAX_REQUEST_QUEUE 100000



static void mcode_or_die(const char *where, CURLMcode code)
{
    if (CURLM_OK != code)
    {
        const char *s;
        #define mycase(code) case code: s = #code 
        switch (code)
        {
        mycase(CURLM_BAD_HANDLE); break;
        mycase(CURLM_BAD_EASY_HANDLE); break;
        mycase(CURLM_OUT_OF_MEMORY); break;
        mycase(CURLM_INTERNAL_ERROR); break;
        mycase(CURLM_UNKNOWN_OPTION); break;
        mycase(CURLM_LAST); break;
        default: s = "CURLM_unknown"; break;
        mycase(CURLM_BAD_SOCKET);
            log_warning("%s returns %s", where, s);
            return;
        }
        log_error("%s returns %s", where, s);
    }
}
static size_t writeCallback(void *ptr, size_t size, size_t nmemb, void *data)
{
    std::string* pStream = static_cast<std::string*>(data);
    if (pStream == NULL) return -1;
    size_t totalWrite = size*nmemb;
    pStream->append((char*)ptr, totalWrite);
    // log_debug("%p,size:%d", ptr, totalWrite);
    return totalWrite;
}

CurlHandler::CurlHandler(CurlClient* client)
{
    Reset();
    m_client = client;
}
CurlHandler::~CurlHandler()
{
    if(NULL != m_head){
        curl_slist_free_all(m_head);
        m_head = NULL;
    }
	if(NULL != m_curl){
		curl_easy_cleanup(m_curl);
		m_curl = NULL;
	}
}

void CurlHandler::Reset(const std::string& url)
{
    if (m_curl == NULL)
    {
        m_curl = curl_easy_init();
    }
    else if ((url.empty()) || (0 != url.compare(m_http_req.url.substr(0, url.size()))))
    {
        curl_easy_reset(m_curl);
    }
    else
    {
        // 同一个url先不处理连接对象
    }

    if(NULL != m_head){
        curl_slist_free_all(m_head);
        m_head = NULL;
    }

    memset(m_error, 0, sizeof(m_error));
    m_http_req.Reset();
    m_http_resp.Reset();
}

void CurlHandler::SetCommon(bool keepalive)
{
	curl_easy_setopt(m_curl, CURLOPT_VERBOSE, 1L);		// 是否打印内部调试信息
	curl_easy_setopt(m_curl, CURLOPT_ERRORBUFFER, m_error);

    // 支持重定向
	curl_easy_setopt(m_curl, CURLOPT_FOLLOWLOCATION, 1L);
    // 
	curl_easy_setopt(m_curl, CURLOPT_PRIVATE, this);

	curl_easy_setopt(m_curl, CURLOPT_NOPROGRESS, 1L);	// 不需要进度信息
	curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYPEER, 0L);	// https支持，不检测
	curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYHOST, 0L);	// https支持，不检测

    if (keepalive)
    {
        curl_easy_setopt(m_curl, CURLOPT_TCP_KEEPALIVE, 1L);  // 启动tcp-keepalive机制
        curl_easy_setopt(m_curl, CURLOPT_TCP_KEEPIDLE, 300L);  // 设置tcp-keepalive时间
        curl_easy_setopt(m_curl, CURLOPT_TCP_KEEPINTVL, 200L); // 设置tcp-keepalive时间间隔
    }
    
    curl_easy_setopt(m_curl, CURLOPT_TIMEOUT_MS, CURL_TIMEOUT_MS);       // 設置超時時間

    // // 保持会话
    // curl_easy_setopt(m_curl, CURLOPT_COOKIESESSION, 1);
    // // 设置共享dns cache功能
    // curl_share_setopt(m_curl, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);

	curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, writeCallback);
	curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &(m_http_resp.body));
}
void CurlHandler::SetRequset(stHttpRequest&& req)
{
    m_http_req = std::move(req);
	// 设置请求的url
	curl_easy_setopt(m_curl, CURLOPT_URL, m_http_req.url.c_str());
	// 设置Http请求信息Header
	if(m_http_req.heads.size() > 0 ){
        char head[2048] = {0};
		for(auto itHead = m_http_req.heads.begin(); itHead != m_http_req.heads.end() ;++itHead)
        {
            bzero(&head, sizeof(head));
            sprintf(head, "%s: %s", itHead->first.c_str(), itHead->second.c_str());
			m_head = curl_slist_append(m_head, head);
		}
		curl_easy_setopt(m_curl, CURLOPT_HTTPHEADER, m_head);
	}

	// 设置post或者get相关的数据
	if( "POST" == m_http_req.method ){
        curl_easy_setopt(m_curl, CURLOPT_POST, 1L);
        curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS,m_http_req.body.c_str());
        curl_easy_setopt(m_curl, CURLOPT_POSTFIELDSIZE, (long)m_http_req.body.size());
    }
}
void CurlHandler::ActivePoll(int fd)
{
    netfd = fd;
	CCReactor::Instance()->AttachPoller(this);
}

int CurlHandler::Init()
{
	// CPollerObject::EnableInput ();
    return 0;
}
int CurlHandler::InputNotify()
{
    return m_client->ProcessOnEvent(netfd, CURL_CSELECT_IN);
}
int CurlHandler::OutputNotify()
{
    return m_client->ProcessOnEvent(netfd, CURL_CSELECT_OUT);
}
int CurlHandler::HangupNotify()
{
    return DetachPoller();
}

int CurlHandler::Run(stHttpRequest&& http_req, stHttpResponse& http_resp)
{
    bool keepalive = true;
    if (0 == CheckHeaderParam(http_req.heads, "Connection", "close"))
    {
        // 明确指明关闭
        keepalive = false;
    }
    SetCommon(keepalive);
    SetRequset(std::move(http_req));

    int err = curl_easy_perform(m_curl);
    curl_easy_getinfo(m_curl, CURLINFO_RESPONSE_CODE, &(m_http_resp.state));
    http_resp = std::move(m_http_resp);
    return err;
}

int CurlHandler::RunAsync(stHttpRequest&& http_req)
{
    if (m_client == NULL)
    {
        return -1;
    }

    bool keepalive = true;
    if (0 == CheckHeaderParam(http_req.heads, "Connection", "close"))
    {
        // 明确指明关闭
        keepalive = false;
    }
    SetCommon(keepalive);
    SetRequset(std::move(http_req));
    
    CURLMcode rc;
    rc = curl_multi_add_handle(m_client->m_pMulti, m_curl);
    mcode_or_die("RunAsync:curl_multi_add_handle", rc);

    return 0;
}

int CurlClient::socketCallback(CURL *curl, curl_socket_t s, int what, void *userp, void *socketp)
{
    CurlClient* client = (CurlClient*)userp;
    CurlHandler* handler = (CurlHandler*)socketp;
    
    // log_debug("curl:%p,fd:%d,socketp:%p", curl, s, socketp);

    bool need_active = (handler == nullptr) ;
    if (need_active) 
    {
        curl_easy_getinfo(curl, CURLINFO_PRIVATE, &handler);
        // log_debug("curl:%p,CURLINFO_PRIVATE:%p", curl, handler);
        if (handler == nullptr)
        {
            return -1;
        }
    }

    if (what == CURL_POLL_REMOVE) 
    {
        handler->DetachPoller();
    }
    else
    {
        if (need_active) 
        {
            handler->ActivePoll(s);
            curl_multi_assign(client->m_pMulti, s, handler);
            log_debug("uuid:%llu,netfd:%d,curl:%p,handler:%p", handler->m_req_id, s, curl, handler);
        }
        handler->EnableInput(what & CURL_POLL_IN);
        handler->EnableOutput(what & CURL_POLL_OUT);
        handler->ApplyEvents();
    }
    return 0;
}
int CurlClient::timeoutCallback(CURLM *m_pMulti, long timeout_ms, CurlClient *client)
{
    // log_debug("timeout_ms:%d", timeout_ms);
    if (timeout_ms >= 0)
    {
        client->m_timer.StartTimer(timeout_ms, false);
    }
    else
    {
        client->m_timer.StopTimer();
    }

    return 0;
}

std::int64_t CurlClient::m_active_client = 0;
CurlClient::CurlClient()
{
    if (m_active_client++ == 0)
    {
        curl_global_init(CURL_GLOBAL_ALL);
    }
}

CurlClient::~CurlClient()
{
	if(NULL != m_pMulti){
		curl_multi_cleanup(m_pMulti);
		m_pMulti = NULL;
	}
    if (--m_active_client == 0)
    {
        curl_global_cleanup();
    }
}

bool CurlClient::Initialize()
{
    if (m_pMulti != nullptr)
    {
        return true;
    }
    m_pMulti = curl_multi_init();
    if (m_pMulti == nullptr)
    {
        return false;
    }

	m_timer.SetTimeEventObj(this, CURL_TIMEOUT_ID);
    
	curl_multi_setopt(m_pMulti, CURLMOPT_SOCKETFUNCTION, socketCallback);
	curl_multi_setopt(m_pMulti, CURLMOPT_SOCKETDATA, this);
	curl_multi_setopt(m_pMulti, CURLMOPT_TIMERFUNCTION, timeoutCallback);
	curl_multi_setopt(m_pMulti, CURLMOPT_TIMERDATA, this);

    return true;
}


int CurlClient::Request(stHttpRequest&& http_req, stHttpResponse& http_resp)
{
    CurlHandler curl_handler(this);
    return curl_handler.Run(std::move(http_req), http_resp);
}

int CurlClient::RequestAsync(std::uint64_t uuid, stHttpRequest&& http_req)
{
    // 正在处理中的+等待处理的数量
    if (m_requestCount + m_request_list.size() > CURL_MAX_REQUEST_PERFORM)
    {
        if (m_request_list.size() >= CURL_MAX_REQUEST_QUEUE)
        {
            return -2;
        }
        CacheRequest(uuid, std::move(http_req));
        return 0;
    }
    CurlHandler* curl_handler = AllocHandler();
    if (curl_handler == NULL)
    {
        CacheRequest(uuid, std::move(http_req));
        return 0;
    }
    return RequestAsync(curl_handler, uuid, std::move(http_req));
}

void CurlClient::CacheRequest(std::uint64_t uuid, stHttpRequest&& http_req)
{
    stHttpRequestQueueItem item;
    item.uuid = uuid;
    item.req = std::move(http_req);
    m_request_list.push(std::move(item));
}
int CurlClient::RequestAsync(CurlHandler* curl_handler, std::uint64_t uuid, stHttpRequest&& http_req)
{
    if (curl_handler == NULL)
    {
        return -1;
    }
    curl_handler->SetReqID(uuid);
    int rc = curl_handler->RunAsync(std::move(http_req));
    log_debug("uuid:%llu,rc:%d,req:%s",uuid, rc, curl_handler->m_http_req.ToString().c_str());
    if (rc == 0)
    {
        m_requestCount++;
    }
    else
    {
        RecycleHandler(curl_handler);
    }
    return rc;
}

void CurlClient::ResetHandler(CurlHandler* curl_handler)
{
    if (curl_handler != nullptr)
    {
        curl_handler->Reset();
    }
}

int CurlClient::ProcessOnTimerOut(int Timerid)
{
    // log_debug("timerid:%d", Timerid);
    switch (Timerid)
    {
    case CURL_TIMEOUT_ID:
	{
        CURLMcode rc;
        rc = curl_multi_socket_action(m_pMulti, CURL_SOCKET_TIMEOUT, 0, &m_still_running);
        mcode_or_die("ProcessOnTimerOut:curl_multi_socket_action", rc);
	    rc = curl_multi_perform(m_pMulti, &m_still_running);
        mcode_or_die("ProcessOnTimerOut:curl_multi_perform", rc);
        check_multi_info();
	}
        break;
    default:
        break;
    }
    
	return 0;
}

int CurlClient::ProcessOnEvent(int fd, int event)
{
    // log_debug("fd:%d,event:%d", fd, event);
    CURLMcode rc;
    rc = curl_multi_socket_action(m_pMulti, event, fd, &m_still_running);
    mcode_or_die("ProcessOnEvent:curl_multi_socket_action", rc);
    rc = curl_multi_perform(m_pMulti, &m_still_running);
    mcode_or_die("ProcessOnEvent:curl_multi_perform", rc);
    check_multi_info();
    return 0;
}

void CurlClient::check_multi_info()
{
    CURLMsg *msg;
    int msgs_left;
    CurlHandler* curl_handler;
    CURL *curl;
    CURLcode res;

    /* See how the transfers went */
    while((msg = curl_multi_info_read(m_pMulti, &msgs_left))) {
        // log_debug("msg:%d,result:%d", msg->msg, msg->data.result);
        if(msg->msg == CURLMSG_DONE) {
            m_requestCount--;
            curl = msg->easy_handle;
            res = msg->data.result;
            curl_easy_getinfo(curl, CURLINFO_PRIVATE, &curl_handler);

			if( res == CURLE_OK ){
				// 返回请求的结果
                log_debug("netfd:%d,uuid:%llu,req:%s,resp:%s",curl_handler->ID(), curl_handler->m_req_id, curl_handler->m_http_req.ToString().c_str(), curl_handler->m_http_resp.ToString().c_str());
                OnResponse(curl_handler->m_req_id, 0, std::move(curl_handler->m_http_resp));
			}else{
				// 请求出错，打印错误结果
				log_error("netfd:%d,uuid:%llu,req=%s,result=%d falied: %s\n", curl_handler->ID(), curl_handler->m_req_id, curl_handler->m_http_req.ToString().c_str(), res, curl_handler->m_error);
				// 返回请求错误
                curl_handler->m_http_resp.body = curl_handler->m_error;
                OnResponse(curl_handler->m_req_id, res, std::move(curl_handler->m_http_resp));
			}
            curl_multi_remove_handle(m_pMulti, curl);

            if (m_request_list.empty())
            {
                // 没有可消耗消息
                RecycleHandler(curl_handler);
                curl_handler = NULL;
            }
            else 
            {
                // 还有可消耗消息,继续处理
                ResetHandler(curl_handler);
                
                auto& item = m_request_list.front();
                std::uint64_t uuid = item.uuid ;
                stHttpRequest req = std::move(item.req);
                m_request_list.pop();
                int rc = RequestAsync(curl_handler, uuid, std::move(req));
                if (rc != 0)
                {
                    OnResponse(uuid, rc, std::move(curl_handler->m_http_resp));
                }
            }
        }
    }
}

void CurlClient::OnResponse(std::uint64_t id, int err, stHttpResponse&& response)
{
    if(m_resp_cb) 
    {
        try
        {
            m_resp_cb(id, err, std::move(response));
        }
        catch(const std::exception& e)
        {
            log_error("exception:%s,id:%llu,err:%d,response:%s", e.what(), id, err, response.ToString().c_str());
        }
        catch (...)
        {
            log_error("unknown exception,id:%llu,err:%d,response:%s", id, err, response.ToString().c_str());
        }
    }
	else
	{
        log_debug("no handler,id:%llu,err:%d,response:%s", id, err, response.ToString().c_str());
	}
}

CurlHandler* CurlClient::AllocHandler()
{
    return new CurlHandler(this);
}

void CurlClient::RecycleHandler(CurlHandler* handler)
{
    if (handler != NULL)
    {
        delete handler;
    }
}



CurlProxy::CurlProxy(const std::string& url, bool keepalive)
{
    m_url = url;
    m_keepalive = keepalive;
}
CurlProxy::~CurlProxy()
{

}

int CurlProxy::Request(stHttpRequest&& http_req, stHttpResponse& http_resp)
{
    if (!CheckRequest(http_req)) return -3;
    return CurlClient::Request(std::move(http_req), http_resp);
}
int CurlProxy::RequestAsync(std::uint64_t uuid, stHttpRequest&& http_req)
{
    if (!CheckRequest(http_req)) return -3;
    return CurlClient::RequestAsync(uuid, std::move(http_req));
}
void CurlProxy::ResetHandler(CurlHandler* curl_handler)
{
    if (curl_handler != nullptr)
    {
        curl_handler->Reset(m_url);
    }
}

bool CurlProxy::CheckRequest(stHttpRequest& http_req) const
{
    if ((m_url.size() > 0) && (0 != m_url.compare(http_req.url.substr(0, m_url.size()))))
    {
        log_debug("url:%s,req_url:%s", m_url.c_str(), http_req.url.c_str());
        return false;
    }

    if (m_keepalive)
    {
        http_req.heads.erase("Connection");
    }
    else 
    {
        http_req.heads["Connection"] = "close";
    }
    return true;
}










